/*!
// Testweb v0.6.0
// http://or-change.cn
// Copyright 2014 OrChange Inc. All rights reserved.
// Licensed under the GPL License.
*/
/*jslint vars: true, forin: true, plusplus: true */
/*global angular: false, engine: false, $: false*/
engine.factory("Process", function (Entity, Result, Step) {
	'use strict';

	var callback = {
		debug_end: function () {
			this.reset();
		},
		before_run: null,
		after_loop: null,
		success: null,
		fail: null
	};

	/**
	 * Mechanism design of its reference computer composition.
	 * It has a program counter, instruction buffer and memory
	 * device. Memory is divided into the program and data segments.
	 * Process inlude Steps and other attributes.
	 *
	 * @class Process
	 * @constructor
	 * @param [counter=0] {number} The current loop is being executed step number.
	 * @param [loop=0] {number} How many times has the programe run.
	 * @param [maxLoops=DEFAULT_loops] {number} The maximum number of runs of the program.
	 * @param [current_step=null] {number} The maximum number of runs of the program.
	 * @param [origin="/"] {string} The original page url of this process.
	 * @example
	 *		new Process({
	 *			name:"xxxx",
	 *			ID: "ADE3910EF8475B31",
	 *			loops:30,
	 *			origin: "/xxx/xx",
	 *			clear_session: true,
	 *			back_to_origin: true
	 *		});
	 */
	var Process = function Process(options) {
		/**
		 * Current loop times.
		 * @property loop
		 * @type number
		 */
		this.loop = 0;
		/**
		 * The maximum number of runs of the program.
		 * @property loops
		 * @type number
		 */
		this.loops = options.loops;
		/**
		 * Programe counter.
		 * @property counter
		 * @type number
		 */
		this.counter = 0;
		/**
		 * Instruction Register.
		 * @property current_step
		 * @type DelayStep|EventStep
		 */
		this.current_step = null;
		/**
		 * Programe segments. Storage all steps.
		 * @property program
		 * @type array
		 */
		this.program = [];

		/**
		 * The original page url of this process.
		 * @property origin
		 * @type string
		 */
		this.origin = options.origin || "/";

		this.config = {
			clear_session: false,
			back_to_origin: true
		};
		/**
		 * Data segments. Storage input data, data dictionary.
		 * @property data_dict
		 * @type object
		 */
		this.dictionary = options.dictionary || null;
		/**
		 * Data segments. Storage output data.
		 * @property result
		 * @type object
		 */
		this.result = new Result(this);
		Entity.call(this, options);
	};
	Process.prototype = new Entity();
	Process.prototype.constructor = Process;

	/**
	 * Controller.
	 * @method $core
	 * @private
	 */
	Process.prototype.$core = function () {
		// Continue to execute if not complete.
		if (typeof callback.before_run === 'function') {
			callback.before_run.call(this);
		}

		if (++this.counter < this.getLength()) {
			// Buff current step object.
			this.current_step = this.program[this.counter];
			this.$execute();

		} else if (++this.loop < this.loops) {
			// Reset counter for next loop. But if all
			// loops have completed, return function.
			this.$callAfterLoopEnd();

			this.current_step = this.program[this.counter = 0];
			this.$execute();
		} else {
			this.$callAfterEnd();
		}
	};
	/**
	 * Execute engine.
	 * @method $execute
	 * @private
	 */
	Process.prototype.$execute = function () {
		this.current_step.run();
	};
	/**
	 * Initialization.
	 * @method $initialize
	 * @returns {Process} this
	 * @private
	 * @chainable
	 */
	Process.prototype.$initialize = function () {
		// 定位起始页
		$('iframe').attr("src", this.origin);
		this.$reset();
		// 缓冲第一步
		this.current_step = this.program[0];

		// 装填字典并发射第一行
		if (this.dictionary !== null) {
			this.dictionary.loading(this.loops);
			this.data = this.dictionary.fetch();
		}

		// 捕捉process快照
		// 创建一条日志
		this.result.setSnapshot();
		this.log = [];

		return this;
	};
	/**
	 * 将循环计数器和程序计数器置为0，将指令寄存器置为null
	 *
	 * @method reset
	 * @private
	 * @chainable
	 */
	Process.prototype.$reset = function () {
		this.loop = 0;
		this.counter = 0;
		this.current_step = null;

		return this;
	};
	/**
	 * 定义一个循环结束要做的事情，可能包括重置会话和返回测试原点
	 *
	 * @method reset
	 * @private
	 * @chainable
	 */
	Process.prototype.$callAfterLoopEnd = function () {
		var key, cookies;

		// 返回起始页
		$('iframe').attr("src", this.origin);

		// 绑定一个loop后的过程
		if (typeof callback.after_loop === "function") {
			callback.after_loop.call(this);
		}

		// 拉一行字典数据
		if (this.dictionary !== null) {
			this.data = this.dictionary.fetch();
		}

		// 创建一个新loop日志
		this.log = [];
	};

	/**
	 * 定义过程结束要做的事情，可能包括重置会话和返回测试原点
	 *
	 * @method reset
	 * @private
	 * @chainable
	 */
	Process.prototype.$callAfterEnd = function () {
		if (typeof callback.success === "function") {
			callback.success.call(this);
		}

		return this;
	};

	Process.prototype.doException = function () {
		// 捕捉异常日志
		this.result.addLog(this.log);

		// 强制完成当前loop重置状态
		this.$callAfterLoopEnd();
		this.loop++;
		this.current_step = this.program[this.counter = 0];

		// 继续执行下一loop
		this.$execute();

		return this;
	};

	Process.prototype.step = function () {
		if (this.counter < this.getLength()) {
			Step.prototype.$run.call(this.program[this.counter], true);
			this.counter++;
		} else {
			callback.debug_end.call(this);
		}

		return this;
	};
	Process.prototype.reset = function () {
		this.$reset();
		this.$initialize();
	};

	/**
	 * 启动这个过程
	 *
	 * @method play
	 * @returns {Process} this
	 * @chainable
	 */
	Process.prototype.play = function () {
		this.$initialize();
		this.$execute();
		return this;
	};
	/**
	 * 暂停这个过程
	 *
	 * @method pause.
	 * @chainable
	 */
	Process.prototype.pause = function () {
		// Clear timers.
		this.current_step.abort();
		return this;
	};
	/**
	 * 停止过程并复位
	 *
	 * @method stop.
	 * @chainable
	 */
	Process.prototype.stop = function () {
		if (this.current_step) {
			this.pause();
		}
		this.$initialize();

		return this;
	};
	/**
	 * 从暂停状态恢复运行。
	 *
	 * @method play
	 * @returns {Process} this
	 * @chainable
	 */
	Process.prototype.resume = function () {
		this.$execute();
		return this;
	};
	/**
	 * 复位并重新启动过程
	 *
	 * @method restartMe
	 */
	Process.prototype.restart = function () {
		this.stop().play();
		return this;
	};

	/**
	 * Add one step object to Process.program
	 * @method addStep
	 * @param {Step} step
	 * @returns {Process} this
	 * @chainable
	 */
	Process.prototype.addStep = function (step) {
		// Take steps to join the program. Set _process of steps
		// current process.
		this.program.push(step.setParent(this));

		return this;
	};
	/**
	 * Insert a Step to program.
	 * @method insertStep
	 * @param index {number} The position of the program queue.
	 * @param step {DelayStep|EventStep} A step need to insert.
	 * @returns {undefined}
	 * @chainable
	 */
	Process.prototype.insertStep = function (index, step) {
		// Insert a step at index of program.
		this.program.splice(index, 0, step);

		return this;
	};
	Process.prototype.replaceStep = function (index, step) {
		this.program[index] = step.setParent(this);

		return this;
	};
	/**
	 * Delete a Step from program.
	 * @method deleteStep
	 * @param index {number} The position of the program queue.
	 * @returns {DelayStep|EventStep} The deleted step.
	 */
	Process.prototype.deleteStep = function (index) {
		// Delete a step from program. Start from
		// index last 1
		return this.program.splice(index, 1);
	};
	/**
	 * 将按index指定的step在program中向上移动
	 *
	 * @method moveStepUp
	 * @param index {number}
	 * @returns {Process}
	 * @chainable
	 */
	Process.prototype.moveStepUp = function (index) {
		var buff;
		if (index > 0) {
			buff = this.program[index - 1];
			this.program[index - 1] = this.program[index];
			this.program[index] = buff;
		}

		return this;
	};
	/**
	 * 将按index指定的step在program中向下移动
	 *
	 * @method moveStepUp
	 * @param index {number}
	 * @returns {Process}
	 * @chainable
	 */
	Process.prototype.moveStepDown = function (index) {
		var buff;
		if (index < this.program.length - 1) {
			buff = this.program[index - 1 + 2];
			this.program[index - 1 + 2] = this.program[index];
			this.program[index] = buff;
		}

		return this;
	};
	Process.prototype.getStep = function (index) {
		return this.program[index];
	};
	/**
	 * Add a dataDict to process by ajax.
	 * @method assignDictionary
	 * @param dictionary {Dictionary}
	 * @returns {Process} this
	 * @chainable
	 */
	Process.prototype.addDictionary = function (dictionary) {
		this.dictionary = dictionary;

		return this;
	};
	Process.prototype.getDictionary = function () {
		return this.dictionary;
	};

	/**
	 * Return the number of steps in the process.
	 * @method getLength
	 * @returns {number} Length of property program.
	 */
	Process.prototype.getLength = function () {
		return this.program.length;
	};
	/**
	 * Set the position of program.
	 * @method setCounter
	 * @param pos {number} The value of PC you want to set.
	 * @returns {Process} this
	 * @chainable
	 */
	Process.prototype.setCounter = function (pos) {
		this.counter = pos;
		return this;
	};

	/**
	 * Get the position of program.
	 * @method getCounter
	 * @returns {number} The value of PC.
	 */
	Process.prototype.getCounter = function () {
		return this.counter;
	};
	Process.prototype.hasDictionary = function () {
		return this.dictionary !== null;
	};

	/**
	 * Drop the whole step program.
	 * @method destroyProgram
	 * @chainable
	 */
	Process.prototype.destroyProgram = function () {
		this.program = [];
		return this;
	};

	Process.prototype.stepsToJson = function () {
		var i, step, steps = [],
			len = this.getLength();
		for (i = 0; i < len; i++) {
			step = this.program[i].toJson();
			step.order = i;
			steps.push(step);
		}

		return steps;
	};

	Process.prototype.toJson = function () {
		var process = {};

		process.name = this.getInfo("name");
		process.loops = this.loops;
		process.origin = this.origin;
		process.comment = this.getInfo("comment");
		process.config = "ffff";
		if (this.dictionary !== null) {
			process.dictionary = this.dictionary.getInfo("ID");
		}

		return process;
	};

	Process.prototype.toOptions = function () {
		var options = {};
		angular.forEach(Entity.prototype.toOptions.call(this), function (value, key) {
			this[key] = value;
		}, options);
		options.origin = this.origin;
		options.loops = this.loops;
		// TODO dictionary
		return options;
	};

	Process.prototype.setupCallback = function (state, func) {
		if (callback.hasOwnProperty(state)) {
			callback[state] = func;
		}

		return this;
	};

	return Process;
});
